<?php

abstract class Xerxes_Z3950
{
	protected $conn;
	protected $query;
	protected $total;
	protected $types = array();
	
	public function __construct($catalog)
	{
		$this->conn = yaz_connect($catalog);
		
		if ( $this->conn === false ) 
		{
			throw new Exception("Cannot connect to '$catalog'");
		}
		
		$this->types["isbn"] = "@attr 1=7"; 
		$this->types["oclc"] = "@attr 1=1007"; 
		$this->types["local"] = "@attr 1=12"; 
	}
	
	public function __destruct()
	{
		// close connection
		yaz_close($this->conn);
	}

	/**
	 * Given a set of bibliographic or item identifiers, returns a list with availability of the
	 * items associated with the identifiers.
	 *
	 * @param string $id			list of either bibliographic or item identifiers, seperate multiple ids by space
	 * @return DLF_List				a list of holdings and item information for each requested bibliographic identifier
	 */
	
	public function getAvailability($id)
	{
		$results = $this->getRecords($id);
		
		foreach ( $results as $record )
		{
			unset($record->bibliographicRecord);
		}
		
		return $results;
	}
	
	/**
	 * Given a list of record identifiers, returns a list of record objects that contain
	 * bibliographic information, as well as associated holdings and item information.
	 *
	 * @param string $id			one or more id numbers, seperate multiple ids by space
	 * @param string $schema		defines the metadata schema in which the records are returned, currently only marc-xml
	 * @return DLF_List				a list of record objects that represents the bibliographic record, as well as
	 * 								associated holdings and item information for each requested bibliographic identifier
	 */
	
	public function getRecords($id)
	{
		$this->query = "";
		
		$final = new DLF_List();
		
		$id = trim($id);
		$ids = explode(" ", $id);
			
		for ( $x = 0; $x < count($ids); $x++)
		{
			$id = explode(":", $ids[$x]);
			$attr = strtolower($id[0]);
			
			// the way it _ought_ to be done, @or-ing all the various
			// ids into a single query
			
			/*
			$this->query .= " " . $this->types[$attr] . " " . $id[1];
			
			if ( $x >= 1 )
			{
				$this->query = " @or " . $this->query;
			}
			*/
			
			// but this is what we are really doing -- innovative server behaves
			// weirdly if we just use the @or above; code is still in place 
			// so we can see if we can't resolve this some other way
			
			$results = $this->doSearch($this->types[$attr] . " " . $id[1]);
			
			if ( $results->length > 0 )
			{
				foreach ( $results->records as $result )
				{					
					foreach ( $final->records as $record_added )
					{
						if ( $record_added == $result )
						{
							continue 2;
						}
					}
					
					array_push($final->records, $result);
				}
			}
		}
		
		return $final;
		
		// return $this->doSearch($this->query);
	}
	
	private function doSearch($query, $start = 1, $max = 10)
	{
		$results = new DLF_List();
		
		// set record type to opac
		
		yaz_syntax($this->conn, "opac");
		
		// prepare query
		
		$search = yaz_search($this->conn, "rpn", $query);
		if ( $search == false ) throw new Exception("Cannot create query");
		
		// prepare record retrieval
		
		$present = yaz_present($this->conn);
		if ( $present == false ) throw new Exception("Cannot create present command");
		
		// now do it for realz, yo!
		
		$response = yaz_wait($this->conn, true);
		if ( $response == false ) throw new Exception("Cannot issue commands to server");
		
		// get any errors
		
		$error = yaz_error($this->conn);
		if ( !empty($error) ) throw new Exception("Yaz error: $error");
		
		// get the total number of hits
		
		$this->total = yaz_hits($this->conn);
				
		// set the end range
		
		$stop = $start + $max - 1;
		
		if ( $stop > $this->total )
		{
			$stop = $this->total;
		}
		
		// loop over the records
		
		$this->total;
		
		if ( $this->total > 0 )
		{
			for ( $x = $start; $x <= $stop; $x++ )
			{
				$record = new DLF_Record();
				
				// get the record
				
				$data = yaz_record($this->conn, $x, "opac; charset=marc-8,utf-8");
				
				if ( $data == "" )
				{
					$data = yaz_record($this->conn, $x, "xml; charset=marc-8,utf-8");
				}
				
				if ( $data == "" )
				{
					continue;
				}
				
				// get just the marc record and add it
				
				$document = new Xerxes_Marc_Document();
				$document->loadXML($data);
				$marc = $document->record();
				
				$record->bibliographicRecord = $marc;
				
				// sub-class will implement this parsing
				
				$xml = simplexml_load_string($data);
				$record->items = $this->parseHoldings($xml);
				
				// echo $xml->asXML();
				
				// add it to results
				
				array_push($results->records, $record);
			}
		}
		
		return $results;
	}
	
	abstract protected function parseHoldings(SimpleXMLElement $record);
}

class DLF_List
{
	private $namespace = "http://diglib.org/ilsdi/1.1";
	public $records = array();
	
	public function toXML()
	{
		$objXml = new DOMDocument();
		$objXml->loadXML("<dlf:collection xmlns:dlf=\"http://diglib.org/ilsdi/1.1\" />");
		
		foreach ( $this->records as $record )
		{
			$objRecord = $objXml->createElementNS($this->namespace, "record");
			$objXml->documentElement->appendChild($objRecord);
			
			if ( isset($record->bibliographicRecord) )
			{
				$objBib = $objXml->createElementNS($this->namespace, "bibliographic");
				$objRecord->appendChild($objBib);
				
				$objMarc = $objXml->importNode($record->bibliographicRecord->getMarcXML()->documentElement, true);
				$objBib->appendChild($objMarc);
			}
			
			if ( count($record->items) > 0 )
			{
				$objItems = $objXml->createElementNS($this->namespace, "items");
				$objRecord->appendChild($objItems);
				
				foreach ( $record->items as $item )
				{
					$objItem = $objXml->createElementNS($this->namespace, "item");
					$objItems->appendChild($objItem);

					$objSimple = $objXml->createElementNS($this->namespace, "simpleavailability");
					$objItem->appendChild($objSimple);						
					
					foreach ( $item as $key => $value )
					{
						if ( $value != "" )
						{
							$objElement = $objXml->createElementNS($this->namespace, $key, $value);
							$objSimple->appendChild($objElement);
						}
					}
				}
			}
		}
		
		return $objXml;
	}
	
	public function __get($name)
	{
		if ( $name == "length")
		{
			return count($this->records);
		}
		else
		{
			throw new Exception("no property '$name' for DLF_List");
		}
	}
}

class DLF_Record
{
	public $bibliographicRecord;
	public $items = array ( ); // array
}

class DLF_Item
{
	public $availabilitystatus; // string
	public $location; // string
	public $call_number; // NON-STANDARD string
	public $availabilitymsg; // string
	public $dateavailable; // string
}

class DLF_NotSupportedException extends Exception {}
class DLF_UnsupportedSchemaException extends Exception {}
class DLF_UnsupportedProfileException extends Exception {}
class DLF_UnsupportedTypeException extends Exception {}

?>